#include <libcpphaggle/Platform.h>
#include "ForwarderProphet.h"
#include "XMLMetadata.h"

#include <math.h>

ForwarderProphet::ForwarderProphet(ForwardingManager *m, const EventType type) :
	ForwarderAsynchronous(m, type, PROPHET_NAME),
	myRank(1), kernel(getManager()->getKernel()), next_id_number(1),
	rib_timestamp(Timeval::now())
{
	// Ensure that the local node's forwarding id is 1:
	id_from_string(kernel->getThisNode()->getIdStr());
	
	HAGGLE_DBG("Forwarding module \'%s\' initialization.\n", getName()); 
}

ForwarderProphet::~ForwarderProphet()
{
}

size_t ForwarderProphet::getSaveState(RepositoryEntryList& rel)
{
	for (prophet_rib_t::iterator it = rib.begin(); it != rib.end(); it++) {
		char value[256];
		snprintf(value, 256, "%s:%ld", (*it).second.first.c_str(), (*it).second.second);
		HAGGLE_DBG("Repository value is %s\n", value);
		rel.push_back(new RepositoryEntry(getName(), id_number_to_nodeid[(*it).first].c_str(), value));
	}
	
	return rel.size();
}

bool ForwarderProphet::setSaveState(RepositoryEntryRef& e)
{
	if (strcmp(e->getAuthority(), getName()) != 0)
		return false;
	
	string value = e->getValueStr();
	
	// Find the separating ':' character in the string
	size_t pos = value.find(':');
	
	prophet_metric_t& metric = rib[id_from_string(e->getKey())];
	
	// The first part of the value is the label metric
	metric.first = value.substr(0, pos).c_str();
	// The second part is the rank
	metric.second = atol(value.substr(pos + 1).c_str());
	
	HAGGLE_DBG("HAGGLE_DBG: orig string=%s label=%s, rank=%ld\n", 
		e->getValue(), metric.first.c_str(), metric.second);
	
	rib_timestamp = Timeval::now();
	
	return true;
}

//get the id number from id sting
prophet_node_id_t ForwarderProphet::id_from_string(const string& nodeid)
{
	prophet_node_id_t &retval = nodeid_to_id_number[nodeid];

	//if this is a new id
	if (retval == 0) {
		retval = next_id_number;
		id_number_to_nodeid[retval] = nodeid;
		next_id_number++;
	}
	return retval;
}

/**
 Add routing information to a data object.
 The parameter "parent" is the location in the data object where the routing 
 information should be inserted.
 */ 
bool ForwarderProphet::addRoutingInformation(DataObjectRef& dObj, Metadata *parent)
{
	if (!dObj || !parent)
		return false;
	
	// Add first our own node ID.
	parent->setParameter("node_id", kernel->getThisNode()->getIdStr());
	
	prophet_rib_t::iterator it;
	
	for (it = rib.begin(); it != rib.end(); it++) {
		if (it->second.first != NULL) {
			Metadata *mm = parent->addMetadata("Metric", (it->second).first);
			mm->setParameter("node_id", id_number_to_nodeid[it->first]);
			mm->setParameter("label", (it->second).first);
			mm->setParameter("rank", (it->second).second);
		}
	}

	dObj->setCreateTime(rib_timestamp);
	return true;
}

/**
 This function is called every time new routing information is received from
 a neighbor. The input is the part of the metadata which contains the
 routing information. This means that the metadata could originally be part 
 of any data object.
 */
bool ForwarderProphet::newRoutingInformation(const Metadata *m)
{	
	if (!m || m->getName() != getName())
		return false;
	
	prophet_node_id_t node_b_id = id_from_string(m->getParameter("node_id"));
	prophet_rib_t &neighbor_rib = neighbor_ribs[node_b_id];
	
	HAGGLE_DBG("New %s routing information received from node [id=%s]\n",
		   getName(),
		   m->getParameter("node_id"));
	
	const Metadata *mm = m->getMetadata("Metric");
	
	while (mm) {
		
		//mm->setParameter("node_id", id_number_to_nodeid[it->first]);
		//mm->setParameter("label", (it->second).first);
		
		prophet_node_id_t node_c_id = id_from_string(mm->getParameter("node_id"));
		
		LABEL_T node_c_label = mm->getParameter("label");
		
		const char* tmp = mm->getParameter("rank");
		
		RANK_T node_c_rank = atol(tmp);
		
		//RANK_T node_c_rank = strtol (mm->getParameter("rank"),);
		
		HAGGLE_DBG(" Received from:%d label:%s rank:%ld",node_c_id, node_c_label.c_str(), node_c_rank);
		
	}
	
	rib_timestamp = Timeval::now();
	
	return true;
}

void ForwarderProphet::_newNeighbor(const NodeRef &neighbor)
{
	// We don't handle routing to anything but other haggle nodes:
	if (neighbor->getType() != Node::TYPE_PEER)
		return;
	
	// Update our private metric regarding this node:
	prophet_node_id_t neighbor_id = id_from_string(neighbor->getIdStr());
	
	LABEL_T &label = rib[neighbor_id].first;
	
	HAGGLE_DBG("HAGGLE_DBG: %s", label.c_str());
	
	rib_timestamp = Timeval::now();
}

void ForwarderProphet::_endNeighbor(const NodeRef &neighbor)
{
	// We don't handle routing to anything but other haggle nodes:
	if (neighbor->getType() != Node::TYPE_PEER)
		return;
	
	// Update our private metric regarding this node:
	prophet_node_id_t neighbor_id = id_from_string(neighbor->getIdStr());
	
	prophet_metric_t &metric = rib[neighbor_id];
	LABEL_T &label = metric.first;
	
	HAGGLE_DBG("HAGGLE_DBG: %s", label.c_str());

	//metric.second = Timeval::now();
	rib_timestamp = metric.second;
}

void ForwarderProphet::_generateTargetsFor(const NodeRef &neighbor)
{

}

static void sortedNodeListInsert(List<Pair<NodeRef, prophet_metric_t> >& list, NodeRef& node, prophet_metric_t metric)
{
	List<Pair<NodeRef,prophet_metric_t> >::iterator it = list.begin();
	
	for (; it != list.end(); it++) {
		//filter
		if (metric.first.compare((*it).second.first) == 0 )
			break;
	}
	
	list.insert(it, make_pair(node, metric));
}

void ForwarderProphet::_generateDelegatesFor(const DataObjectRef &dObj, const NodeRef &target, const NodeRefList *other_targets)
{
	List<Pair<NodeRef, prophet_metric_t> > sorted_delegate_list;
	
	// Figure out which node to look for:
	prophet_node_id_t target_id = id_from_string(target->getIdStr());
	
	LABEL_T targetLabel = rib[target_id].first;
	
	// Go through the neighbor's forwarding table:
	for (Map<prophet_node_id_t, prophet_rib_t>::iterator it = neighbor_ribs.begin(); it != neighbor_ribs.end(); it++) {

		if (it->first != this_node_id && it->first != target_id) {

			NodeRef delegate = kernel->getNodeStore()->retrieve(id_number_to_nodeid[it->first], true);

			if (delegate && !isTarget(delegate, other_targets)) {
				
				for (prophet_rib_t::iterator pit = it->second.begin(); pit != it->second.end(); pit++)
				{
					LABEL_T &neighborLabel = pit->second.first;
					RANK_T &neighborRank = pit->second.second;
					
					if (neighborLabel==targetLabel && neighborRank > myRank)
					{
						sortedNodeListInsert(sorted_delegate_list, delegate, pit->second);
						//HAGGLE_DBG("Node '%s' is a good delegate for target '%s' [my_metric=%s, neighbor_metric=%s]\n", delegate->getName().c_str(), target->getName().c_str(), P_ad, P_bd);
					}
				}
			}
		}
	}
	
	// Add up to max_generated_delegates delegates to the result in order of decreasing metric
	if (!sorted_delegate_list.empty()) {
		NodeRefList delegates;
		unsigned long num_delegates = max_generated_delegates;
		
		while (num_delegates && sorted_delegate_list.size()) {
			NodeRef delegate = sorted_delegate_list.front().first;
			sorted_delegate_list.pop_front();
			delegates.push_back(delegate);
			num_delegates--;
		}
		kernel->addEvent(new Event(EVENT_TYPE_DELEGATE_NODES, dObj, target, delegates));
		HAGGLE_DBG("Generated %lu delegates for target %s\n", delegates.size(), target->getName().c_str());
	} else {
                HAGGLE_DBG("No delegates found for target %s\n", target->getName().c_str());
        }
}

void ForwarderProphet::_onForwarderConfig(const Metadata& m)
{
	if (strcmp(getName(), m.getName().c_str()) != 0)
		return;
	
	HAGGLE_DBG("Prophet forwarder configuration\n");
	
	const char *param = m.getParameter("label");
	if (param) {
		
		myLabel = string(param);
		HAGGLE_DBG("%s: Setting label to %s\n", getName(), myLabel.c_str());
	}
	
	param = m.getParameter("rank");
	if (param)
	{
		char *ptr = NULL;
		RANK_T p = strtol(param,&ptr,10);
		
		if (ptr && ptr != param && *ptr == '\0')
		{
			myRank = p;
			HAGGLE_DBG("%s: Setting rank to %ld\n", getName(), myRank);
		}
	}
	
}

void ForwarderProphet::_printRoutingTable(void)
{
	printf("%s routing table:\n", getName());
	
	for (Map<prophet_node_id_t, string>::iterator it = 
	     id_number_to_nodeid.begin(); it != id_number_to_nodeid.end(); it++) {
		HAGGLE_DBG("%ld: %s\n", it->first, it->second.c_str());
	}
		
	printf("internal: {");
	
	prophet_rib_t::iterator jt = rib.begin();
	
	while (jt != rib.end()) {
		// Only age metrics if the node is not a neighbor
		bool isNeighbor = getKernel()->getNodeStore()->stored(id_number_to_nodeid[jt->first], true);
		
		HAGGLE_DBG("%ld: %s", jt->first, isNeighbor ? jt->second.first.c_str() : jt->second.first.c_str());
		jt++;
		if (jt != rib.end())
			printf(", ");
	}
	printf("}\n");
	
	for (Map<prophet_node_id_t, prophet_rib_t>::iterator it = 
	     neighbor_ribs.begin(); it != neighbor_ribs.end(); it++) {
		printf("%ld: {", it->first);;
		prophet_rib_t::iterator jt = it->second.begin();
		while (jt != it->second.end()) {
			HAGGLE_DBG("%ld: %s", jt->first, jt->second.first.c_str());
			jt++;
			if (jt != it->second.end())
				printf(", ");
		}
		printf("}\n");
	}
}

